This document introduces various techniques for making staic maps from spatial data.

Base R Graphics

Pure Base R

Vector Data in sp Format

Example 1

A map with Spatial Points and Spatial Polygons, where the size of the point is scaled to the ‘zinc’ level in the soil at that point.

suppressPackageStartupMessages(library(sp))
demo(meuse, ask = FALSE, echo = FALSE) 
crs.longlat = CRS("+init=epsg:4326")
meuse.longlat = spTransform(meuse, crs.longlat)
meuse.riv.longlat <- spTransform(meuse.riv, crs.longlat)
grid.lines <- gridlines(meuse.longlat)

par(mar = c(1, 1, 1, 1))
plot(methods::as(meuse.longlat, "Spatial"), expandBB=c(0.05,0,0.1,0))
plot(grid.lines, add = TRUE, col = grey(.8))
plot(meuse.longlat, pch=1, cex = sqrt(meuse$zinc)/12, add = TRUE)
text(labels(grid.lines, side=2:3), col = grey(.7), offset=1.5)
v = c(100,200,400,800,1600)
legend("bottomright", legend = v, pch = 1, pt.cex = sqrt(v)/12,
       text.col =grey(.8), box.col=grey(0.8), title='Zinc Conc. (ppm)')
plot(meuse.riv.longlat, add = TRUE, col = grey(.9, alpha = .5))

Example 2

World map in Winkel-triple projection showing all cities with population > 1Million. The size of the circle representing the city is proportional to its population.

library(maps)
data(world.cities)
world.cities <- world.cities[world.cities$pop>1000000,]
coordinates(world.cities) <- ~long+lat 
proj4string(world.cities) <- '+init=epsg:4326'

world <- rnaturalearth::countries110
world <- world[world$name != 'Antarctica',]

grid.lines.mj <- gridlines(world,easts = seq(-180,180,by=30), norths = seq(-90,90,by=30))
grid.lines.mi <- gridlines(world,easts = seq(-165,195,by=15), norths = seq(-90,90,by=15))
world.cities <- spTransform(world.cities, CRS("+proj=wintri"))
world <- spTransform(world, CRS("+proj=wintri"))
grid.lines.mj <- spTransform(grid.lines.mj,CRS("+proj=wintri"))
grid.lines.mi <- spTransform(grid.lines.mi,CRS("+proj=wintri"))

par(mar = c(8, 0.1, 0.1, 0.1))
plot(methods::as(world, 'Spatial'), expandBB=c(0,0,0.05,0.05))

plot(grid.lines.mi, col=grey(0.95), add=T)
plot(grid.lines.mj, col=grey(0.9), add=T)
text(labels(grid.lines.mj, side=1:2, labelCRS = CRS("+init=epsg:4326")), col = grey(.6), offset=0.3)

plot(world, add=TRUE, border=grey(0.2), col=grey(0.9))
plot(world.cities, add=TRUE, col='#FF5A0088', pch=20,
     cex=world.cities$pop/2000000)

v = c(1,4,8,12)
legend("topright", legend = v, pch = 20, pt.cex = v/2,
       text.col =grey(.7), box.col=grey(0.9),
       col = '#FF5A0088',
       title='Pop. (Millions)', horiz =T)

Raster Data

Mesus data set.

suppressPackageStartupMessages(library(raster))
r <- raster(system.file("external/test.grd", package="raster"))
plot(r)
plot(meuse, add=T) # Add Vector Data
box(); title('Raster + Vector')

Using cartography Package

Plot of Internet usage as percentage of the population of Afrincan Countries.

suppressPackageStartupMessages(library(cartography))

world <- sf::st_as_sf(rnaturalearth::countries110)
internet_usage <- suppressMessages(readr::read_csv(
  system.file(
    'extdata', 'africa-internet_usage-2015.csv',
    package = 'user2017.geodataviz')))

africa <- dplyr::filter(world, region_un=='Africa') %>%
  dplyr::left_join(internet_usage %>% dplyr::select(
    `Country Code`, `2015 [YR2015]`
  ) %>% dplyr::rename(iso_a3=`Country Code`, internet.usage.2015=`2015 [YR2015]`),
  by = 'iso_a3') %>%
  st_transform(crs="+proj=laea +lon_0=18.984375")

par(mar = c(0.5, 0.5, 0.5, 0.5))
plot(st_geometry(africa), border=grey(0.2), col=grey(0.9))
plot(st_centroid(africa), add=TRUE, col='black', pch=20)
{{propSymbolsLayer(x=africa, var='internet.usage.2015', inches = 0.3, col = '#FF5A0088')}}

Plotting using ggplot2

Just ggplot2

Vector Data in sp Format

Choropleth from estimated median GDP of European countries.

world <- rnaturalearth::countries110
europe <- world[world$region_un=="Europe"&world$name!='Russia',]
# plot(europe)

# Let's add a unique ID column to our data.
{{europe@data$id <- row.names(europe@data)}}

# A bounding box for continental Europe.
europe.bbox <- SpatialPolygons(list(Polygons(list(Polygon(
  matrix(c(-25,29,45,29,45,75,-25,75,-25,29),byrow = T,ncol = 2)
)), ID = 1)), proj4string = CRS(proj4string(europe)))

# Get polygons that are only in continental Europe.
europe.clipped <-
{{  rgeos::gIntersection(europe, europe.bbox, byid = TRUE, id=europe$id)}}

# tidy up the data for ggplot2
europe.tidy <- broom::tidy(europe.clipped)
europe.tidy <- dplyr::left_join(europe.tidy, europe@data, by='id')
library(ggplot2)
ggplot(europe.tidy, aes(long,lat, group=group,fill=gdp_md_est/1000)) +
  geom_polygon(alpha=0.8,color='black') +
  coord_map("azequalarea") +
  hrbrthemes::theme_ipsum_rc() +
  viridis::scale_fill_viridis(
    name='Median GDP \n(in Billions)', direction = -1, labels=scales::dollar) +
  labs(x=NULL, y=NULL, 
       title='Median GDP Estimates of\nContinental Europe & Iceland',
       caption='Source: http://www.naturalearthdata.com/')

Vector Data in sf Format

Choropleth from estimated median GDP of European countries.

suppressPackageStartupMessages(library(sf))

world <- st_as_sf(rnaturalearth::countries110)
europe <- dplyr::filter(world, region_un=="Europe" & name!='Russia')

# A bounding box for continental Europe.
europe.bbox <- st_polygon(list(
  matrix(c(-25,29,45,29,45,75,-25,75,-25,29),byrow = T,ncol = 2)))

europe.clipped <- suppressWarnings(st_intersection(europe, st_sfc(europe.bbox, crs=st_crs(europe))))


ggplot(europe.clipped, aes(fill=gdp_md_est/1000)) +
  geom_sf(alpha=0.8,col='white') +
  coord_sf(crs="+proj=aea +lat_1=36.333333333333336 +lat_2=65.66666666666667 +lon_0=14") +
  hrbrthemes::theme_ipsum_rc() +
  viridis::scale_fill_viridis(
    name='Median GDP \n(in Billions)', direction = -1, labels=scales::dollar) +
  labs(x=NULL, y=NULL, title=NULL,
       caption='Source: http://www.naturalearthdata.com/')

Raster Data

suppressPackageStartupMessages(library(raster))

r <- raster(system.file("external/test.grd", package="raster"))

{{rasterVis::gplot(r) + geom_tile(aes(fill = value)) }} +
  viridis::scale_fill_viridis(direction = -1, na.value='#FFFFFF00') +
  coord_equal() + hrbrthemes::theme_ipsum()

Using ggspatial Package

ggspatial package avoids the need to tidy spatial data, by providing geom_spatial() which works natively with spatial data. It also provides ggosm() function to show a basemap from tile map providers such as Open Street Map (OSM).

library(sp)
library(ggplot2)
library(ggspatial)
demo(meuse,ask=F, echo = F)
ggosm(type = "cartolight",quiet = TRUE) +
  geom_spatial(meuse)
Converting coordinates to lat/lon
Zoom: 14

Using ggmap Package

ggmap provides a myrid of add-on functionality for plotting maps using ggplot2.

suppressPackageStartupMessages(library(ggplot2))
suppressPackageStartupMessages(library(ggmap))

paste0("2016-0",1:7) %>%
  purrr::map(function(month) {
    suppressMessages(readr::read_csv(
      system.file(
        sprintf("examples/data/London-Crimes/%s/%s-city-of-london-street.csv.zip",
                month,month),
        package='leaflet.extras')
    ))
  }) %>%
  dplyr::bind_rows() -> crimes

suppressMessages(suppressWarnings(
  qmplot(Longitude, Latitude,
         data = crimes %>% dplyr::filter(!is.na(Latitude)),
         geom="blank", zoom=15,
         maptype = "toner-lite", facets = ~Month) +
  stat_density_2d(aes(fill = ..level..), geom = "polygon", alpha = .3) +
    scale_fill_gradient2("Crime Heatmap", low = "white", mid = "yellow", high = "red")  + theme(legend.position = 'hide')
))

Plotting using tmap

tmap provides quick and easy thematic mapping. tmap output cannot be combined either base R plot() output or ggplot2 output. tmap has its own API similar to ggplot2, but a few subtle differences.

usa_pop_history <- suppressMessages(
  readr::read_tsv(system.file(
    'extdata','usa_pop_history.tsv', package='user2017.geodataviz')))
usa <- suppressWarnings(dplyr::left_join(albersusa::usa_sf(), usa_pop_history, by=c('name'='State')))
usa <- st_transform(usa, crs=albersusa::us_laea_proj)
years <-  c(1900,1950,1990,2000,2010,2015)
print(tmap::qtm(usa, fill=paste0("p.",years), title=years, 
          layout.title.color='red',  layout.title.position=c('center','top'),
          layout.main.title = 'U.S.A. Population by State',
          style = 'col_blind', facets.nrow=2))

Animations

Using animation

library(ggplot2)

usa <- albersusa::usa_sf()

usa_pop_history <- readr::read_tsv(system.file(
'extdata','usa_pop_history.tsv', package='user2017.geodataviz'))

usa <- dplyr::left_join(usa, usa_pop_history, by=c('name'='State'))

g <- ggplot(data=usa) + ggthemes::theme_map() + theme(legend.position = 'bottom') +
  viridis::scale_fill_viridis(direction = -1, option = 'A')

g.2015 <- g + geom_sf(aes(fill=p.2015)) + coord_sf(crs = albersusa::us_aeqd_proj)
g.2010 <- g + geom_sf(aes(fill=p.2010)) + coord_sf(crs = albersusa::us_aeqd_proj)
g.2000 <- g + geom_sf(aes(fill=p.2000)) + coord_sf(crs = albersusa::us_aeqd_proj)
g.1990 <- g + geom_sf(aes(fill=p.1990)) + coord_sf(crs = albersusa::us_aeqd_proj)
g.1950 <- g + geom_sf(aes(fill=p.1950)) + coord_sf(crs = albersusa::us_aeqd_proj)
g.1900 <- g + geom_sf(aes(fill=p.1900)) + coord_sf(crs = albersusa::us_aeqd_proj)

# Save each file
ggsave(g.2015,filename = '/tmp/g_2015.png')
ggsave(g.2010,filename = '/tmp/g_2010.png')
ggsave(g.2000,filename = '/tmp/g_2000.png')
ggsave(g.1990,filename = '/tmp/g_1990.png')
ggsave(g.1950,filename = '/tmp/g_1950.png')
ggsave(g.1900,filename = '/tmp/g_1900.png')

animation::im.convert(files = '/tmp/g_*.png', output =  'animation-01.gif')
unlink('/tmp/g_*.png', force = TRUE)
LS0tCnRpdGxlOiAiU3BhdGlhbCBEYXRhIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogVFJVRQogICAgdG9jX2Zsb2F0OiBUUlVFCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogY29uc29sZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQpvcHRpb25zKGh0bWx0b29scy5kaXIudmVyc2lvbiA9IEZBTFNFKQprbml0cjo6b3B0c19jaHVuayRzZXQoY2FjaGUgPSBUUlVFKQprbml0cjo6b3B0c19jaHVuayRzZXQoZGV2ID0gJ3N2ZycpCm9wdGlvbnMoZGV2aWNlID0gZnVuY3Rpb24oZmlsZSwgd2lkdGgsIGhlaWdodCkgewogIHN2Zyh0ZW1wZmlsZSgpLCB3aWR0aCA9IHdpZHRoLCBoZWlnaHQgPSBoZWlnaHQpCn0pCmBgYAoKVGhpcyBkb2N1bWVudCBpbnRyb2R1Y2VzIHZhcmlvdXMgdGVjaG5pcXVlcyBmb3IgbWFraW5nIHN0YWljIG1hcHMgZnJvbSBzcGF0aWFsIGRhdGEuCgojIEJhc2UgUiBHcmFwaGljcwoKIyMgUHVyZSBCYXNlIFIgCgojIyMgVmVjdG9yIERhdGEgaW4gYHNwYCBGb3JtYXQKCiMjIyMgRXhhbXBsZSAxCgpBIG1hcCB3aXRoIFNwYXRpYWwgUG9pbnRzIGFuZCBTcGF0aWFsIFBvbHlnb25zLCB3aGVyZSB0aGUgc2l6ZSBvZiB0aGUgcG9pbnQgaXMgc2NhbGVkIHRvIHRoZSAnemluYycgbGV2ZWwgaW4gdGhlIHNvaWwgYXQgdGhhdCBwb2ludC4KCmBgYHtyIDAxLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD00LCBmaWcuYWxpZ249J2NlbnRlcid9CnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KHNwKSkKZGVtbyhtZXVzZSwgYXNrID0gRkFMU0UsIGVjaG8gPSBGQUxTRSkgCmNycy5sb25nbGF0ID0gQ1JTKCIraW5pdD1lcHNnOjQzMjYiKQptZXVzZS5sb25nbGF0ID0gc3BUcmFuc2Zvcm0obWV1c2UsIGNycy5sb25nbGF0KQptZXVzZS5yaXYubG9uZ2xhdCA8LSBzcFRyYW5zZm9ybShtZXVzZS5yaXYsIGNycy5sb25nbGF0KQpncmlkLmxpbmVzIDwtIGdyaWRsaW5lcyhtZXVzZS5sb25nbGF0KQoKcGFyKG1hciA9IGMoMSwgMSwgMSwgMSkpCnBsb3QobWV0aG9kczo6YXMobWV1c2UubG9uZ2xhdCwgIlNwYXRpYWwiKSwgZXhwYW5kQkI9YygwLjA1LDAsMC4xLDApKQpwbG90KGdyaWQubGluZXMsIGFkZCA9IFRSVUUsIGNvbCA9IGdyZXkoLjgpKQpwbG90KG1ldXNlLmxvbmdsYXQsIHBjaD0xLCBjZXggPSBzcXJ0KG1ldXNlJHppbmMpLzEyLCBhZGQgPSBUUlVFKQp0ZXh0KGxhYmVscyhncmlkLmxpbmVzLCBzaWRlPTI6MyksIGNvbCA9IGdyZXkoLjcpLCBvZmZzZXQ9MS41KQp2ID0gYygxMDAsMjAwLDQwMCw4MDAsMTYwMCkKbGVnZW5kKCJib3R0b21yaWdodCIsIGxlZ2VuZCA9IHYsIHBjaCA9IDEsIHB0LmNleCA9IHNxcnQodikvMTIsCiAgICAgICB0ZXh0LmNvbCA9Z3JleSguOCksIGJveC5jb2w9Z3JleSgwLjgpLCB0aXRsZT0nWmluYyBDb25jLiAocHBtKScpCnBsb3QobWV1c2Uucml2LmxvbmdsYXQsIGFkZCA9IFRSVUUsIGNvbCA9IGdyZXkoLjksIGFscGhhID0gLjUpKQpgYGAKCiMjIyMgRXhhbXBsZSAyCgpXb3JsZCBtYXAgaW4gV2lua2VsLXRyaXBsZSBwcm9qZWN0aW9uIHNob3dpbmcgYWxsIGNpdGllcyB3aXRoIHBvcHVsYXRpb24gPiAxTWlsbGlvbi4gVGhlIHNpemUgb2YgdGhlIGNpcmNsZSByZXByZXNlbnRpbmcgdGhlIGNpdHkgaXMgcHJvcG9ydGlvbmFsIHRvIGl0cyBwb3B1bGF0aW9uLgoKCmBgYHtyIDAyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD02LCBmaWcuYWxpZ249J2NlbnRlcid9CmxpYnJhcnkobWFwcykKZGF0YSh3b3JsZC5jaXRpZXMpCndvcmxkLmNpdGllcyA8LSB3b3JsZC5jaXRpZXNbd29ybGQuY2l0aWVzJHBvcD4xMDAwMDAwLF0KY29vcmRpbmF0ZXMod29ybGQuY2l0aWVzKSA8LSB+bG9uZytsYXQgCnByb2o0c3RyaW5nKHdvcmxkLmNpdGllcykgPC0gJytpbml0PWVwc2c6NDMyNicKCndvcmxkIDwtIHJuYXR1cmFsZWFydGg6OmNvdW50cmllczExMAp3b3JsZCA8LSB3b3JsZFt3b3JsZCRuYW1lICE9ICdBbnRhcmN0aWNhJyxdCgpncmlkLmxpbmVzLm1qIDwtIGdyaWRsaW5lcyh3b3JsZCxlYXN0cyA9IHNlcSgtMTgwLDE4MCxieT0zMCksIG5vcnRocyA9IHNlcSgtOTAsOTAsYnk9MzApKQpncmlkLmxpbmVzLm1pIDwtIGdyaWRsaW5lcyh3b3JsZCxlYXN0cyA9IHNlcSgtMTY1LDE5NSxieT0xNSksIG5vcnRocyA9IHNlcSgtOTAsOTAsYnk9MTUpKQp3b3JsZC5jaXRpZXMgPC0gc3BUcmFuc2Zvcm0od29ybGQuY2l0aWVzLCBDUlMoIitwcm9qPXdpbnRyaSIpKQp3b3JsZCA8LSBzcFRyYW5zZm9ybSh3b3JsZCwgQ1JTKCIrcHJvaj13aW50cmkiKSkKZ3JpZC5saW5lcy5taiA8LSBzcFRyYW5zZm9ybShncmlkLmxpbmVzLm1qLENSUygiK3Byb2o9d2ludHJpIikpCmdyaWQubGluZXMubWkgPC0gc3BUcmFuc2Zvcm0oZ3JpZC5saW5lcy5taSxDUlMoIitwcm9qPXdpbnRyaSIpKQoKcGFyKG1hciA9IGMoOCwgMC4xLCAwLjEsIDAuMSkpCnBsb3QobWV0aG9kczo6YXMod29ybGQsICdTcGF0aWFsJyksIGV4cGFuZEJCPWMoMCwwLDAuMDUsMC4wNSkpCgpwbG90KGdyaWQubGluZXMubWksIGNvbD1ncmV5KDAuOTUpLCBhZGQ9VCkKcGxvdChncmlkLmxpbmVzLm1qLCBjb2w9Z3JleSgwLjkpLCBhZGQ9VCkKdGV4dChsYWJlbHMoZ3JpZC5saW5lcy5taiwgc2lkZT0xOjIsIGxhYmVsQ1JTID0gQ1JTKCIraW5pdD1lcHNnOjQzMjYiKSksIGNvbCA9IGdyZXkoLjYpLCBvZmZzZXQ9MC4zKQoKcGxvdCh3b3JsZCwgYWRkPVRSVUUsIGJvcmRlcj1ncmV5KDAuMiksIGNvbD1ncmV5KDAuOSkpCnBsb3Qod29ybGQuY2l0aWVzLCBhZGQ9VFJVRSwgY29sPScjRkY1QTAwODgnLCBwY2g9MjAsCiAgICAgY2V4PXdvcmxkLmNpdGllcyRwb3AvMjAwMDAwMCkKCnYgPSBjKDEsNCw4LDEyKQpsZWdlbmQoInRvcHJpZ2h0IiwgbGVnZW5kID0gdiwgcGNoID0gMjAsIHB0LmNleCA9IHYvMiwKICAgICAgIHRleHQuY29sID1ncmV5KC43KSwgYm94LmNvbD1ncmV5KDAuOSksCiAgICAgICBjb2wgPSAnI0ZGNUEwMDg4JywKICAgICAgIHRpdGxlPSdQb3AuIChNaWxsaW9ucyknLCBob3JpeiA9VCkKYGBgCgojIyMgUmFzdGVyIERhdGEKCk1lc3VzIGRhdGEgc2V0LgoKYGBge3IgMDMsIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTUsIGZpZy5hbGlnbj0nY2VudGVyJ30Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkocmFzdGVyKSkKciA8LSByYXN0ZXIoc3lzdGVtLmZpbGUoImV4dGVybmFsL3Rlc3QuZ3JkIiwgcGFja2FnZT0icmFzdGVyIikpCnBsb3QocikKcGxvdChtZXVzZSwgYWRkPVQpICMgQWRkIFZlY3RvciBEYXRhCmJveCgpOyB0aXRsZSgnUmFzdGVyICsgVmVjdG9yJykKYGBgCgojIyBVc2luZyBgY2FydG9ncmFwaHlgIFBhY2thZ2UKClBsb3Qgb2YgSW50ZXJuZXQgdXNhZ2UgYXMgcGVyY2VudGFnZSBvZiB0aGUgcG9wdWxhdGlvbiBvZiBBZnJpbmNhbiBDb3VudHJpZXMuCgpgYGB7ciAwNCwgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NSwgZmlnLmFsaWduPSdjZW50ZXInfQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGlicmFyeShjYXJ0b2dyYXBoeSkpCgp3b3JsZCA8LSBzZjo6c3RfYXNfc2Yocm5hdHVyYWxlYXJ0aDo6Y291bnRyaWVzMTEwKQppbnRlcm5ldF91c2FnZSA8LSBzdXBwcmVzc01lc3NhZ2VzKHJlYWRyOjpyZWFkX2NzdigKICBzeXN0ZW0uZmlsZSgKICAgICdleHRkYXRhJywgJ2FmcmljYS1pbnRlcm5ldF91c2FnZS0yMDE1LmNzdicsCiAgICBwYWNrYWdlID0gJ3VzZXIyMDE3Lmdlb2RhdGF2aXonKSkpCgphZnJpY2EgPC0gZHBseXI6OmZpbHRlcih3b3JsZCwgcmVnaW9uX3VuPT0nQWZyaWNhJykgJT4lCiAgZHBseXI6OmxlZnRfam9pbihpbnRlcm5ldF91c2FnZSAlPiUgZHBseXI6OnNlbGVjdCgKICAgIGBDb3VudHJ5IENvZGVgLCBgMjAxNSBbWVIyMDE1XWAKICApICU+JSBkcGx5cjo6cmVuYW1lKGlzb19hMz1gQ291bnRyeSBDb2RlYCwgaW50ZXJuZXQudXNhZ2UuMjAxNT1gMjAxNSBbWVIyMDE1XWApLAogIGJ5ID0gJ2lzb19hMycpICU+JQogIHN0X3RyYW5zZm9ybShjcnM9Iitwcm9qPWxhZWEgK2xvbl8wPTE4Ljk4NDM3NSIpCgpwYXIobWFyID0gYygwLjUsIDAuNSwgMC41LCAwLjUpKQpwbG90KHN0X2dlb21ldHJ5KGFmcmljYSksIGJvcmRlcj1ncmV5KDAuMiksIGNvbD1ncmV5KDAuOSkpCnBsb3Qoc3RfY2VudHJvaWQoYWZyaWNhKSwgYWRkPVRSVUUsIGNvbD0nYmxhY2snLCBwY2g9MjApCnt7cHJvcFN5bWJvbHNMYXllcih4PWFmcmljYSwgdmFyPSdpbnRlcm5ldC51c2FnZS4yMDE1JywgaW5jaGVzID0gMC4zLCBjb2wgPSAnI0ZGNUEwMDg4Jyl9fQpgYGAKCiMgUGxvdHRpbmcgdXNpbmcgYGdncGxvdDJgCgojIyBKdXN0IGBnZ3Bsb3QyYAoKIyMjIFZlY3RvciBEYXRhIGluIGBzcGAgRm9ybWF0CgpDaG9yb3BsZXRoIGZyb20gZXN0aW1hdGVkIG1lZGlhbiBHRFAgb2YgRXVyb3BlYW4gY291bnRyaWVzLgoKYGBge3IgMDUsIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTUsIGZpZy5hbGlnbj0nY2VudGVyJ30Kd29ybGQgPC0gcm5hdHVyYWxlYXJ0aDo6Y291bnRyaWVzMTEwCmV1cm9wZSA8LSB3b3JsZFt3b3JsZCRyZWdpb25fdW49PSJFdXJvcGUiJndvcmxkJG5hbWUhPSdSdXNzaWEnLF0KIyBwbG90KGV1cm9wZSkKCiMgTGV0J3MgYWRkIGEgdW5pcXVlIElEIGNvbHVtbiB0byBvdXIgZGF0YS4Ke3tldXJvcGVAZGF0YSRpZCA8LSByb3cubmFtZXMoZXVyb3BlQGRhdGEpfX0KCiMgQSBib3VuZGluZyBib3ggZm9yIGNvbnRpbmVudGFsIEV1cm9wZS4KZXVyb3BlLmJib3ggPC0gU3BhdGlhbFBvbHlnb25zKGxpc3QoUG9seWdvbnMobGlzdChQb2x5Z29uKAogIG1hdHJpeChjKC0yNSwyOSw0NSwyOSw0NSw3NSwtMjUsNzUsLTI1LDI5KSxieXJvdyA9IFQsbmNvbCA9IDIpCikpLCBJRCA9IDEpKSwgcHJvajRzdHJpbmcgPSBDUlMocHJvajRzdHJpbmcoZXVyb3BlKSkpCgojIEdldCBwb2x5Z29ucyB0aGF0IGFyZSBvbmx5IGluIGNvbnRpbmVudGFsIEV1cm9wZS4KZXVyb3BlLmNsaXBwZWQgPC0Ke3sgIHJnZW9zOjpnSW50ZXJzZWN0aW9uKGV1cm9wZSwgZXVyb3BlLmJib3gsIGJ5aWQgPSBUUlVFLCBpZD1ldXJvcGUkaWQpfX0KCiMgdGlkeSB1cCB0aGUgZGF0YSBmb3IgZ2dwbG90MgpldXJvcGUudGlkeSA8LSBicm9vbTo6dGlkeShldXJvcGUuY2xpcHBlZCkKZXVyb3BlLnRpZHkgPC0gZHBseXI6OmxlZnRfam9pbihldXJvcGUudGlkeSwgZXVyb3BlQGRhdGEsIGJ5PSdpZCcpCmxpYnJhcnkoZ2dwbG90MikKZ2dwbG90KGV1cm9wZS50aWR5LCBhZXMobG9uZyxsYXQsIGdyb3VwPWdyb3VwLGZpbGw9Z2RwX21kX2VzdC8xMDAwKSkgKwogIGdlb21fcG9seWdvbihhbHBoYT0wLjgsY29sb3I9J2JsYWNrJykgKwogIGNvb3JkX21hcCgiYXplcXVhbGFyZWEiKSArCiAgaHJicnRoZW1lczo6dGhlbWVfaXBzdW1fcmMoKSArCiAgdmlyaWRpczo6c2NhbGVfZmlsbF92aXJpZGlzKAogICAgbmFtZT0nTWVkaWFuIEdEUCBcbihpbiBCaWxsaW9ucyknLCBkaXJlY3Rpb24gPSAtMSwgbGFiZWxzPXNjYWxlczo6ZG9sbGFyKSArCiAgbGFicyh4PU5VTEwsIHk9TlVMTCwgCiAgICAgICB0aXRsZT0nTWVkaWFuIEdEUCBFc3RpbWF0ZXMgb2ZcbkNvbnRpbmVudGFsIEV1cm9wZSAmIEljZWxhbmQnLAogICAgICAgY2FwdGlvbj0nU291cmNlOiBodHRwOi8vd3d3Lm5hdHVyYWxlYXJ0aGRhdGEuY29tLycpCgpgYGAKCiMjIyBWZWN0b3IgRGF0YSBpbiBgc2ZgIEZvcm1hdAoKQ2hvcm9wbGV0aCBmcm9tIGVzdGltYXRlZCBtZWRpYW4gR0RQIG9mIEV1cm9wZWFuIGNvdW50cmllcy4KCmBgYHtyIDA2LCBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD01LCBmaWcuYWxpZ249J2NlbnRlcid9CnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KHNmKSkKCndvcmxkIDwtIHN0X2FzX3NmKHJuYXR1cmFsZWFydGg6OmNvdW50cmllczExMCkKZXVyb3BlIDwtIGRwbHlyOjpmaWx0ZXIod29ybGQsIHJlZ2lvbl91bj09IkV1cm9wZSIgJiBuYW1lIT0nUnVzc2lhJykKCiMgQSBib3VuZGluZyBib3ggZm9yIGNvbnRpbmVudGFsIEV1cm9wZS4KZXVyb3BlLmJib3ggPC0gc3RfcG9seWdvbihsaXN0KAogIG1hdHJpeChjKC0yNSwyOSw0NSwyOSw0NSw3NSwtMjUsNzUsLTI1LDI5KSxieXJvdyA9IFQsbmNvbCA9IDIpKSkKCmV1cm9wZS5jbGlwcGVkIDwtIHN1cHByZXNzV2FybmluZ3Moc3RfaW50ZXJzZWN0aW9uKGV1cm9wZSwgc3Rfc2ZjKGV1cm9wZS5iYm94LCBjcnM9c3RfY3JzKGV1cm9wZSkpKSkKCgpnZ3Bsb3QoZXVyb3BlLmNsaXBwZWQsIGFlcyhmaWxsPWdkcF9tZF9lc3QvMTAwMCkpICsKICBnZW9tX3NmKGFscGhhPTAuOCxjb2w9J3doaXRlJykgKwogIGNvb3JkX3NmKGNycz0iK3Byb2o9YWVhICtsYXRfMT0zNi4zMzMzMzMzMzMzMzMzMzYgK2xhdF8yPTY1LjY2NjY2NjY2NjY2NjY3ICtsb25fMD0xNCIpICsKICBocmJydGhlbWVzOjp0aGVtZV9pcHN1bV9yYygpICsKICB2aXJpZGlzOjpzY2FsZV9maWxsX3ZpcmlkaXMoCiAgICBuYW1lPSdNZWRpYW4gR0RQIFxuKGluIEJpbGxpb25zKScsIGRpcmVjdGlvbiA9IC0xLCBsYWJlbHM9c2NhbGVzOjpkb2xsYXIpICsKICBsYWJzKHg9TlVMTCwgeT1OVUxMLCB0aXRsZT1OVUxMLAogICAgICAgY2FwdGlvbj0nU291cmNlOiBodHRwOi8vd3d3Lm5hdHVyYWxlYXJ0aGRhdGEuY29tLycpCmBgYAoKIyMjIFJhc3RlciBEYXRhCgpgYGB7ciAwNywgZmlnLndpZHRoPTYsIGZpZy5oZWlnaHQ9NSwgZmlnLmFsaWduPSdjZW50ZXInfQoKc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkocmFzdGVyKSkKCnIgPC0gcmFzdGVyKHN5c3RlbS5maWxlKCJleHRlcm5hbC90ZXN0LmdyZCIsIHBhY2thZ2U9InJhc3RlciIpKQoKe3tyYXN0ZXJWaXM6OmdwbG90KHIpICsgZ2VvbV90aWxlKGFlcyhmaWxsID0gdmFsdWUpKSB9fSArCiAgdmlyaWRpczo6c2NhbGVfZmlsbF92aXJpZGlzKGRpcmVjdGlvbiA9IC0xLCBuYS52YWx1ZT0nI0ZGRkZGRjAwJykgKwogIGNvb3JkX2VxdWFsKCkgKyBocmJydGhlbWVzOjp0aGVtZV9pcHN1bSgpCmBgYAoKIyMgVXNpbmcgYGdnc3BhdGlhbGAgUGFja2FnZQoKYGdnc3BhdGlhbGAgcGFja2FnZSBhdm9pZHMgdGhlIG5lZWQgdG8gdGlkeSBzcGF0aWFsIGRhdGEsIGJ5IHByb3ZpZGluZyBgZ2VvbV9zcGF0aWFsKClgIHdoaWNoIHdvcmtzIG5hdGl2ZWx5IHdpdGggc3BhdGlhbCBkYXRhLiBJdCBhbHNvIHByb3ZpZGVzIGBnZ29zbSgpYCBmdW5jdGlvbiB0byBzaG93IGEgYmFzZW1hcCBmcm9tIHRpbGUgbWFwIHByb3ZpZGVycyBzdWNoIGFzIE9wZW4gU3RyZWV0IE1hcCAoT1NNKS4KCmBgYHtyIDA4LCBmaWcud2lkdGg9MywgZmlnLmhlaWdodD00LCBmaWcuYWxpZ249J2NlbnRlcid9CmxpYnJhcnkoc3ApCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShnZ3NwYXRpYWwpCmRlbW8obWV1c2UsYXNrPUYsIGVjaG8gPSBGKQpnZ29zbSh0eXBlID0gImNhcnRvbGlnaHQiLHF1aWV0ID0gVFJVRSkgKwogIGdlb21fc3BhdGlhbChtZXVzZSkKCmBgYAoKIyMgVXNpbmcgYGdnbWFwYCBQYWNrYWdlCgpgZ2dtYXBgIHByb3ZpZGVzIGEgbXlyaWQgb2YgYWRkLW9uIGZ1bmN0aW9uYWxpdHkgZm9yIHBsb3R0aW5nIG1hcHMgdXNpbmcgYGdncGxvdDJgLgoKYGBge3IgMDksIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD02LCBmaWcuYWxpZ249J2NlbnRlcid9CnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KGdncGxvdDIpKQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGlicmFyeShnZ21hcCkpCgpwYXN0ZTAoIjIwMTYtMCIsMTo3KSAlPiUKICBwdXJycjo6bWFwKGZ1bmN0aW9uKG1vbnRoKSB7CiAgICBzdXBwcmVzc01lc3NhZ2VzKHJlYWRyOjpyZWFkX2NzdigKICAgICAgc3lzdGVtLmZpbGUoCiAgICAgICAgc3ByaW50ZigiZXhhbXBsZXMvZGF0YS9Mb25kb24tQ3JpbWVzLyVzLyVzLWNpdHktb2YtbG9uZG9uLXN0cmVldC5jc3YuemlwIiwKICAgICAgICAgICAgICAgIG1vbnRoLG1vbnRoKSwKICAgICAgICBwYWNrYWdlPSdsZWFmbGV0LmV4dHJhcycpCiAgICApKQogIH0pICU+JQogIGRwbHlyOjpiaW5kX3Jvd3MoKSAtPiBjcmltZXMKCnN1cHByZXNzTWVzc2FnZXMoc3VwcHJlc3NXYXJuaW5ncygKICBxbXBsb3QoTG9uZ2l0dWRlLCBMYXRpdHVkZSwKICAgICAgICAgZGF0YSA9IGNyaW1lcyAlPiUgZHBseXI6OmZpbHRlcighaXMubmEoTGF0aXR1ZGUpKSwKICAgICAgICAgZ2VvbT0iYmxhbmsiLCB6b29tPTE1LAogICAgICAgICBtYXB0eXBlID0gInRvbmVyLWxpdGUiLCBmYWNldHMgPSB+TW9udGgpICsKICBzdGF0X2RlbnNpdHlfMmQoYWVzKGZpbGwgPSAuLmxldmVsLi4pLCBnZW9tID0gInBvbHlnb24iLCBhbHBoYSA9IC4zKSArCiAgICBzY2FsZV9maWxsX2dyYWRpZW50MigiQ3JpbWUgSGVhdG1hcCIsIGxvdyA9ICJ3aGl0ZSIsIG1pZCA9ICJ5ZWxsb3ciLCBoaWdoID0gInJlZCIpICArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdoaWRlJykKKSkKYGBgCgojIFBsb3R0aW5nIHVzaW5nIGB0bWFwYAoKYHRtYXBgIHByb3ZpZGVzIHF1aWNrIGFuZCBlYXN5IHRoZW1hdGljIG1hcHBpbmcuIGB0bWFwYCBvdXRwdXQgY2Fubm90IGJlIGNvbWJpbmVkIGVpdGhlciBiYXNlIFIgYHBsb3QoKWAgb3V0cHV0IG9yIGBnZ3Bsb3QyYCBvdXRwdXQuIGB0bWFwYCBoYXMgaXRzIG93biBBUEkgc2ltaWxhciB0byBgZ2dwbG90MmAsIGJ1dCBhIGZldyBzdWJ0bGUgZGlmZmVyZW5jZXMuCgoKYGBge3IgMTAsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD02LCBmaWcuYWxpZ249J2NlbnRlcid9CnVzYV9wb3BfaGlzdG9yeSA8LSBzdXBwcmVzc01lc3NhZ2VzKAogIHJlYWRyOjpyZWFkX3RzdihzeXN0ZW0uZmlsZSgKICAgICdleHRkYXRhJywndXNhX3BvcF9oaXN0b3J5LnRzdicsIHBhY2thZ2U9J3VzZXIyMDE3Lmdlb2RhdGF2aXonKSkpCnVzYSA8LSBzdXBwcmVzc1dhcm5pbmdzKGRwbHlyOjpsZWZ0X2pvaW4oYWxiZXJzdXNhOjp1c2Ffc2YoKSwgdXNhX3BvcF9oaXN0b3J5LCBieT1jKCduYW1lJz0nU3RhdGUnKSkpCnVzYSA8LSBzdF90cmFuc2Zvcm0odXNhLCBjcnM9YWxiZXJzdXNhOjp1c19sYWVhX3Byb2opCnllYXJzIDwtICBjKDE5MDAsMTk1MCwxOTkwLDIwMDAsMjAxMCwyMDE1KQpwcmludCh0bWFwOjpxdG0odXNhLCBmaWxsPXBhc3RlMCgicC4iLHllYXJzKSwgdGl0bGU9eWVhcnMsIAogICAgICAgICAgbGF5b3V0LnRpdGxlLmNvbG9yPSdyZWQnLCAgbGF5b3V0LnRpdGxlLnBvc2l0aW9uPWMoJ2NlbnRlcicsJ3RvcCcpLAogICAgICAgICAgbGF5b3V0Lm1haW4udGl0bGUgPSAnVS5TLkEuIFBvcHVsYXRpb24gYnkgU3RhdGUnLAogICAgICAgICAgc3R5bGUgPSAnY29sX2JsaW5kJywgZmFjZXRzLm5yb3c9MikpCmBgYAoKCiMgQW5pbWF0aW9ucwoKIyMgVXNpbmcgYGFuaW1hdGlvbmAKCmBgYHtyIDExLCBpbmNsdWRlPUZBTFNFfQpsaWJyYXJ5KGdncGxvdDIpCgp1c2EgPC0gYWxiZXJzdXNhOjp1c2Ffc2YoKQoKdXNhX3BvcF9oaXN0b3J5IDwtIHJlYWRyOjpyZWFkX3RzdihzeXN0ZW0uZmlsZSgKJ2V4dGRhdGEnLCd1c2FfcG9wX2hpc3RvcnkudHN2JywgcGFja2FnZT0ndXNlcjIwMTcuZ2VvZGF0YXZpeicpKQoKdXNhIDwtIGRwbHlyOjpsZWZ0X2pvaW4odXNhLCB1c2FfcG9wX2hpc3RvcnksIGJ5PWMoJ25hbWUnPSdTdGF0ZScpKQoKZyA8LSBnZ3Bsb3QoZGF0YT11c2EpICsgZ2d0aGVtZXM6OnRoZW1lX21hcCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ2JvdHRvbScpICsKICB2aXJpZGlzOjpzY2FsZV9maWxsX3ZpcmlkaXMoZGlyZWN0aW9uID0gLTEsIG9wdGlvbiA9ICdBJykKCmcuMjAxNSA8LSBnICsgZ2VvbV9zZihhZXMoZmlsbD1wLjIwMTUpKSArIGNvb3JkX3NmKGNycyA9IGFsYmVyc3VzYTo6dXNfYWVxZF9wcm9qKQpnLjIwMTAgPC0gZyArIGdlb21fc2YoYWVzKGZpbGw9cC4yMDEwKSkgKyBjb29yZF9zZihjcnMgPSBhbGJlcnN1c2E6OnVzX2FlcWRfcHJvaikKZy4yMDAwIDwtIGcgKyBnZW9tX3NmKGFlcyhmaWxsPXAuMjAwMCkpICsgY29vcmRfc2YoY3JzID0gYWxiZXJzdXNhOjp1c19hZXFkX3Byb2opCmcuMTk5MCA8LSBnICsgZ2VvbV9zZihhZXMoZmlsbD1wLjE5OTApKSArIGNvb3JkX3NmKGNycyA9IGFsYmVyc3VzYTo6dXNfYWVxZF9wcm9qKQpnLjE5NTAgPC0gZyArIGdlb21fc2YoYWVzKGZpbGw9cC4xOTUwKSkgKyBjb29yZF9zZihjcnMgPSBhbGJlcnN1c2E6OnVzX2FlcWRfcHJvaikKZy4xOTAwIDwtIGcgKyBnZW9tX3NmKGFlcyhmaWxsPXAuMTkwMCkpICsgY29vcmRfc2YoY3JzID0gYWxiZXJzdXNhOjp1c19hZXFkX3Byb2opCgojIFNhdmUgZWFjaCBmaWxlCmdnc2F2ZShnLjIwMTUsZmlsZW5hbWUgPSAnL3RtcC9nXzIwMTUucG5nJykKZ2dzYXZlKGcuMjAxMCxmaWxlbmFtZSA9ICcvdG1wL2dfMjAxMC5wbmcnKQpnZ3NhdmUoZy4yMDAwLGZpbGVuYW1lID0gJy90bXAvZ18yMDAwLnBuZycpCmdnc2F2ZShnLjE5OTAsZmlsZW5hbWUgPSAnL3RtcC9nXzE5OTAucG5nJykKZ2dzYXZlKGcuMTk1MCxmaWxlbmFtZSA9ICcvdG1wL2dfMTk1MC5wbmcnKQpnZ3NhdmUoZy4xOTAwLGZpbGVuYW1lID0gJy90bXAvZ18xOTAwLnBuZycpCgphbmltYXRpb246OmltLmNvbnZlcnQoZmlsZXMgPSAnL3RtcC9nXyoucG5nJywgb3V0cHV0ID0gICdhbmltYXRpb24tMDEuZ2lmJykKdW5saW5rKCcvdG1wL2dfKi5wbmcnLCBmb3JjZSA9IFRSVUUpCmBgYAoKCmBgYHtyIDEyLCBldmFsPUZBTFNFfQpsaWJyYXJ5KGdncGxvdDIpCgp1c2EgPC0gYWxiZXJzdXNhOjp1c2Ffc2YoKQoKdXNhX3BvcF9oaXN0b3J5IDwtIHJlYWRyOjpyZWFkX3RzdihzeXN0ZW0uZmlsZSgKJ2V4dGRhdGEnLCd1c2FfcG9wX2hpc3RvcnkudHN2JywgcGFja2FnZT0ndXNlcjIwMTcuZ2VvZGF0YXZpeicpKQoKdXNhIDwtIGRwbHlyOjpsZWZ0X2pvaW4odXNhLCB1c2FfcG9wX2hpc3RvcnksIGJ5PWMoJ25hbWUnPSdTdGF0ZScpKQoKZyA8LSBnZ3Bsb3QoZGF0YT11c2EpICsgZ2d0aGVtZXM6OnRoZW1lX21hcCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ2JvdHRvbScpICsKICB2aXJpZGlzOjpzY2FsZV9maWxsX3ZpcmlkaXMoZGlyZWN0aW9uID0gLTEsIG9wdGlvbiA9ICdBJykKCmcuMjAxNSA8LSBnICsgZ2VvbV9zZihhZXMoZmlsbD1wLjIwMTUpKSArIGNvb3JkX3NmKGNycyA9IGFsYmVyc3VzYTo6dXNfYWVxZF9wcm9qKQpnLjIwMTAgPC0gZyArIGdlb21fc2YoYWVzKGZpbGw9cC4yMDEwKSkgKyBjb29yZF9zZihjcnMgPSBhbGJlcnN1c2E6OnVzX2FlcWRfcHJvaikKZy4yMDAwIDwtIGcgKyBnZW9tX3NmKGFlcyhmaWxsPXAuMjAwMCkpICsgY29vcmRfc2YoY3JzID0gYWxiZXJzdXNhOjp1c19hZXFkX3Byb2opCmcuMTk5MCA8LSBnICsgZ2VvbV9zZihhZXMoZmlsbD1wLjE5OTApKSArIGNvb3JkX3NmKGNycyA9IGFsYmVyc3VzYTo6dXNfYWVxZF9wcm9qKQpnLjE5NTAgPC0gZyArIGdlb21fc2YoYWVzKGZpbGw9cC4xOTUwKSkgKyBjb29yZF9zZihjcnMgPSBhbGJlcnN1c2E6OnVzX2FlcWRfcHJvaikKZy4xOTAwIDwtIGcgKyBnZW9tX3NmKGFlcyhmaWxsPXAuMTkwMCkpICsgY29vcmRfc2YoY3JzID0gYWxiZXJzdXNhOjp1c19hZXFkX3Byb2opCgojIFNhdmUgZWFjaCBmaWxlCmdnc2F2ZShnLjIwMTUsZmlsZW5hbWUgPSAnL3RtcC9nXzIwMTUucG5nJykKZ2dzYXZlKGcuMjAxMCxmaWxlbmFtZSA9ICcvdG1wL2dfMjAxMC5wbmcnKQpnZ3NhdmUoZy4yMDAwLGZpbGVuYW1lID0gJy90bXAvZ18yMDAwLnBuZycpCmdnc2F2ZShnLjE5OTAsZmlsZW5hbWUgPSAnL3RtcC9nXzE5OTAucG5nJykKZ2dzYXZlKGcuMTk1MCxmaWxlbmFtZSA9ICcvdG1wL2dfMTk1MC5wbmcnKQpnZ3NhdmUoZy4xOTAwLGZpbGVuYW1lID0gJy90bXAvZ18xOTAwLnBuZycpCgphbmltYXRpb246OmltLmNvbnZlcnQoZmlsZXMgPSAnL3RtcC9nXyoucG5nJywgb3V0cHV0ID0gICdhbmltYXRpb24tMDEuZ2lmJykKdW5saW5rKCcvdG1wL2dfKi5wbmcnLCBmb3JjZSA9IFRSVUUpCmBgYAoKIVtdKGFuaW1hdGlvbi0wMS5naWYpCg==